package idx

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"os"
	"path/filepath"
	"strings"
)

const Ns = 10 //大文件的最大数量 = Ns^2

func calIdxFile(key []byte) string {
	crc := crc32.ChecksumIEEE(key)
	dir := (crc >> 24) % Ns
	name := (crc & 0x00ff0000 >> 16) % Ns
	return fmt.Sprintf("%d/%d", dir+1, name+1)
}

func openIdxDat(fileName string) (fd *FileDat, err error) {
	fp, size, err := OpenFile(datFile(fileName))
	if err != nil {
		return
	}
	fd, err = NewFileDat(fp, size)
	return
}

func datFile(fileName string) string {
	return fileName + ".dat"
}

func cleanFile(fileName string) string {
	return fileName + "_cleanup_"
}

func isDatFile(fileName string) bool {
	return strings.HasSuffix(fileName, ".dat")
}

func isCleanFile(fileName string) bool {
	return strings.HasSuffix(fileName, "_cleanup_.dat")
}

// OpenFile 都写模式打开文件，返回文件句柄和大小， 不存在会尝试创建文件及对应目录
func OpenFile(f string) (*os.File, int64, error) {
	_, err := os.Stat(f)
	var fp *os.File
	var size int64
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, 0, err
		}
		//尝试创建目录
		dir := filepath.Dir(f)
		fsd, _err := os.Stat(dir)
		if _err != nil {
			if !os.IsNotExist(_err) {
				return nil, 0, _err
			}
			err = os.MkdirAll(dir, FileMod)
			if err != nil {
				return nil, 0, err
			}
		} else if !fsd.IsDir() {
			return nil, 0, errors.New(dir + " is not dir")
		}
		//创建文件
		fp, err = os.OpenFile(f, os.O_RDWR|os.O_CREATE|os.O_TRUNC, FileMod)
	} else {
		fp, err = os.OpenFile(f, os.O_RDWR, FileMod)
		fstat, _err := fp.Stat()
		if _err != nil {
			return nil, 0, _err
		}
		size = fstat.Size()
	}
	if err != nil {
		return nil, 0, err
	}
	return fp, size, nil
}

// getFile 返回文件句柄，文件大小，不创建
func GetFile(f string) (*os.File, int64, error) {
	fstat, err := os.Stat(f)
	var fp *os.File
	if err != nil {
		return nil, 0, err
	}
	fp, err = os.Open(f)
	if err != nil {
		return nil, 0, err
	}
	return fp, fstat.Size(), nil
}

// loadFileIdx s将idx文件load到map
func loadFileIdx(fp *os.File) (head Head, maps map[string]*Idx, err error) {
	fstat, err := fp.Stat()
	if err != nil {
		return
	}
	max := fstat.Size()
	//小于四个字节，开始初始化文件
	if max < HeadLength {
		b := bytes.Buffer{}
		head = newHead(Ver)
		_ = binary.Write(&b, binary.BigEndian, head)
		_, _err := fp.WriteAt(b.Bytes(), 0)
		if _err != nil {
			err = fmt.Errorf("初始化文件失败：%s", _err)
			return
		}
		max = HeadLength
	} else {
		b := make([]byte, HeadLength)
		_, _err := fp.ReadAt(b, 0)
		if _err != nil {
			err = fmt.Errorf("读取文件头失败：%s", _err)
			return
		}
		head = Head(binary.BigEndian.Uint32(b[:HeadLength]))
	}

	offset := int64(HeadLength)
	maps = make(map[string]*Idx)
	for {
		if offset >= max {
			break
		}
		b := make([]byte, 4)
		_, err = fp.ReadAt(b, offset)
		if err != nil {
			return
		}
		ih := IdxHead(binary.BigEndian.Uint32(b))
		currLength := ih.Count()
		//读取数据
		b = make([]byte, currLength)
		_n, _err := fp.ReadAt(b, offset)
		if _n != len(b) {
			err = errors.New("读取数据错误")
			return
		}
		if _err != nil && _err != io.EOF {
			err = _err
			return
		}
		idx := FromBytes(b)
		//标记了删除的跳过
		if idx.Del() {
			offset += currLength + idx.Size
			continue
		}
		maps[string(idx.Name)] = idx
		offset += currLength + idx.Size
	}
	return
}
